package br.com.sebrae.pr.sgp.util.exceptions;

import java.sql.BatchUpdateException;
import java.sql.SQLException;

import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;

/**
 * Classe auxiliar para conversão das exceções do sistema.
 * 
 * @author Tanaka
 */
public class TratadorDeExcecoes {

	private static final Logger log = Logger
			.getLogger(TratadorDeExcecoes.class);

	private static final long MAX_EXCECOES_CAUSA = 500;

	/**
	 * Converte determinada exceção para uma exceção de apresentação.
	 * 
	 * @param e
	 * @return
	 */
	public static ExcecaoDeApresentacao convertException(Throwable e) {

		ExcecaoDeApresentacao excecaoDeApresentacao = null;
		ExcecaoDeBancoDeDados exBanco = null;

		if (e != null) {
			if (e instanceof ExcecaoDeNegocio) {
				final ExcecaoDeNegocio en = (ExcecaoDeNegocio) e;
				return new ExcecaoDeApresentacao(TipoErro.ERRO, en.getMsgKey(),
						en.getParams(), en);

			} else if (e instanceof ExcecaoDeBancoDeDados) {
				exBanco = (ExcecaoDeBancoDeDados) e;

			} else if ((e instanceof ExcecaoDeInfraEstrutura)
					&& (e.getCause() != null)) {
				Exception ex = (Exception) e;
				long count = 0;
				while (ex.getCause() != null && count < MAX_EXCECOES_CAUSA) {
					ex = (Exception) e.getCause();

					exBanco = recuperaExcecaoDeBancoRaiz(ex);

					if (exBanco != null) {
						break;
					}
					count++;
				}
			} else {
				// verifica se a excecao atual não é uma excecao de banco
				exBanco = recuperaExcecaoDeBancoRaiz(e);
			}

			// verifica se foi encontrado dados de alguma excecao de banco
			if (exBanco != null) {

				switch (exBanco.getCodErroBanco().intValue()) {
				case 1:
					// Unique constraint violated
					excecaoDeApresentacao = new ExcecaoDeApresentacao(
							TipoErro.ERRO, "app.ora0001",
							new String[] { StringUtils.isNotBlank(exBanco
									.getMsgErroBanco()) ? exBanco
									.getMsgErroBanco()
									: "Sem mensagem do banco" }, exBanco);
					break;
				default:
					break;
				}
			}
		}

		if (excecaoDeApresentacao == null) {
			String descricaoExcecao = null;

			Throwable ex = e;

			if (exBanco != null) {
				descricaoExcecao = ", EXCECAO: "
						+ ex.getClass().getSimpleName() + " COD BANCO: "
						+ exBanco.getCodErroBanco() + " MSG: "
						+ exBanco.getMsgErroBanco();
			} else {
				if (ex != null) {
					while (ex.getCause() != null) {
						ex = (Exception) ex.getCause();
					}
				}

				descricaoExcecao = "EXCECAO RAIZ: "
						+ ((ex != null) ? ex.getClass().getName() : null)
						+ " MENSAGEM: "
						+ ((ex != null) ? ex.getMessage() : null);
			}
			logaException(descricaoExcecao, ex);
			excecaoDeApresentacao = geraMensagemGenerica(descricaoExcecao, ex);
		}

		return excecaoDeApresentacao;
	}

	/**
	 * Gera uma exceção genérica, para os erros não tratados.
	 * 
	 * @param descricaoAdicional
	 * @return
	 */
	public static ExcecaoDeApresentacao geraMensagemGenerica(
			final String descricaoAdicional, final Throwable cause) {
		return new ExcecaoDeApresentacao(TipoErro.ERRO, "app.erroNaoEsperado",
				new String[] { descricaoAdicional }, cause);
	}

	/**
	 * Recupera a exceção raiz do {@link Throwable}.
	 * 
	 * @param e
	 * @return
	 */
	public static ExcecaoDeBancoDeDados recuperaExcecaoDeBancoRaiz(
			final Throwable e) {
		ExcecaoDeBancoDeDados exBanco = null;

		if (e instanceof SQLException) {
			final SQLException sqlEx = resolveNestedSqlException((SQLException) e);
			exBanco = new ExcecaoDeBancoDeDados(Integer.valueOf(sqlEx
					.getErrorCode()), sqlEx.getMessage());

		} else if (e instanceof BatchUpdateException) {
			final BatchUpdateException ex = (BatchUpdateException) e;

			if (ex.getNextException() != null) {
				final SQLException sqlEx = resolveNestedSqlException(ex
						.getNextException());
				exBanco = new ExcecaoDeBancoDeDados(Integer.valueOf(sqlEx
						.getErrorCode()), sqlEx.getMessage());

			} else {
				exBanco = new ExcecaoDeBancoDeDados(Integer.valueOf(ex
						.getErrorCode()), ex.getMessage());
			}
		}

		if (exBanco != null) {
			String msgErroBanco = exBanco.getMsgErroBanco();

			if (msgErroBanco.indexOf(':') > -1) {
				msgErroBanco = msgErroBanco.substring(
						exBanco.getMsgErroBanco().indexOf(':') + 1).trim();

				if (msgErroBanco.indexOf('\n') > -1) {
					msgErroBanco = msgErroBanco.split("\n")[0];
				}

				exBanco.setMsgErroBanco(msgErroBanco);
			}
		}

		return exBanco;
	}

	/**
	 * Obtém a causa raiz de determinada {@link SQLException}.
	 * 
	 * @param ex
	 * @return
	 */
	private static SQLException resolveNestedSqlException(final SQLException ex) {
		SQLException sqlEx = ex;

		if (sqlEx.getNextException() != null) {
			SQLException nEx = ex.getNextException();

			while (nEx.getNextException() != null) {
				nEx = nEx.getNextException();
			}

			sqlEx = nEx;
		}

		return sqlEx;
	}

	public static void logaException(final String mensagem, final Throwable t) {
		if (!(t instanceof ExcecaoDeApresentacao)
				&& !(t instanceof ExcecaoDeNegocio)) {
			log.error(mensagem, t);
		}
	}

}
